Skip to main content

Update state

In this part you'll learn how to properly update state stored in stock

Checkboxes

Let's add checkboxes to our todos.

TodoList.jsx
import { useStockValue, useStockContext } from 'stocked';

export const TodoList = () => {
const { setValue } = useStockContext();
const todos = useStockValue('todos');

return (
<ul>
{todos.map(({ title }, key) => (
<li key={key}>
<input
id={`todo-${key}`}
type="checkbox"
checked={completed}
onChange={(e) =>
setValue(`todos[${key}].completed`, e.target.checked)
}
/>
<label htmlFor={`todo-${key}`}>{title}</label>
</li>
))}
</ul>
);
};

As you can see, we're changing todo item's state via setValue function. This function deeply sets value in object.

This means, that when our value in stock is:

{
todos: [
{
title: 'Some title'
completed: false,
}
]
}

We can access completed variable by path todos[0].completed.

But, now, when user clicks on checkbox whole app re-renders. We can view it thanks to React developer tools:

Performance

note

Cyan outline indicates which component re-renders

Let's fix this issue!

Optimization

Firstly, let's isolate todo item state. To do this, we'll create a new component TodoItem.

TodoItem.jsx
import { useCallback } from 'react';
import { useStockValue, useStockState } from 'stocked';

export const TodoItem = ({ index }) => {
const title = useStockValue(`todos[${index}].title`);
const [completed, setCompleted] = useStockState(`todos[${index}].completed`);

const onChange = useCallback((e) => {
setCompleted(e.target.checked);
}, [setCompleted]);

return (
<li>
<input
id={`todo-${index}`}
type="checkbox"
checked={completed}
onChange={onChange}
/>
<label htmlFor={`todo-${index}`}>{title}</label>
</li>
);
}

Then, we need to change our TodoList component:

TodoItem.jsx
import { useStockValue } from "stocked";
import { TodoItem } from "./TodoItem";

export const TodoList = () => {
const todos = useStockValue("todos");

return (
<ul>
{todos.map((_, key) => (
<TodoItem key={key} index={key} />
))}
</ul>
);
};

As you can see, we don't need todos array to render them - we need only count. So, let's get only array length inside TodoList component:

TodoList.jsx
import { useStockValue } from "stocked";
import { TodoItem } from "./TodoItem";

export const TodoList = () => {
const todoCount = useStockValue("todos.length");

return (
<ul>
{new Array(todoCount).fill(0).map((_, key) => (
<TodoItem key={key} index={key} />
))}
</ul>
);
};

So, let's check our performance now:

Optimized list&#39;s performance

Perfect! Now, only one todo is highlighted!

New todo

Finally, let's create possibility to add new todo. For this feature, we will create NewTodo component:

NewTodo.jsx
import { useCallback } from 'react';
import { useStockState, useStockContext } from 'stocked';

export const NewTodo = () => {
const { getValue, setValue } = useStockContext();

const [title, setTitle] = useStockState('newTodo.title');

const onChange = useCallback((e) => {
setTitle(e.target.value);
}, [setTitle]);

const createNewTodo = useCallback(() => {
const currentTitle = getValue('newTodo.title');
const currentTodos = getValue('todos');

setValue('todos', [...currentTodos, {
title: currentTitle,
completed: false,
}]);

setValue('newTodo.title', '');
}, [getValue, setValue]);

return (
<div>
<div>
<label htmlFor="new-title">New todo:</label>
<input id="new-title" value={title} onChange={onChange} />
</div>
<button onClick={createNewTodo}>+ Add</button>
</div>
);
};

And use this component in App:

App.jsx
import { NewTodo } from "./NewTodo";

// ...

function App() {
return (
<StockRoot
initialValues={{
/** */
newTodo: {
title: ''
}
}}
>
<h1>Todo list!</h1>
<NewTodo />
<TodoList />
</StockRoot>
);
}

export default App;

Intermediate result

And this is what we have at the moment:

Result

Let's move on.