'Line-through' style in react blinking for some reason - javascript

I've fetched a todo data from jsonplaceholder.typicode and I set up a action using redux fetching that data (I use thunk middleware). And I successfully get that data from jsonplaceholder to my component and I add a logic in reducer for toggling the todos:
//initState is { todos: [] }
case TOGGLE_TODO:
return {
...state,
todos: state.todos.map(todo => {
if (todo.id === action.payload) {
return {
...todo, completed: !todo.completed
}
}
return todo
})
}
But the problem is when I toggle a todo using checkbox, the 'line-through' style is blinking for some reason (it shows the strike in text but disappearing after I think .5 sec ) thats why I need a understanding why it happens, Is it because I fetched it in the internet? Or somethings wrong it the logic? Sorry for my noob question.
Here's my Todo component:
const dispatch = useDispatch()
const strikeStyle = {
textDecoration: todo.completed ? 'line-through' : ''
}
const onChangeHandler = () => {
dispatch(toggleTodo(todo.id))
}
...
<label>
<input onChange={onChangeHandler} type='checkbox' />
<p style={strikeStyle}>{todo.title}</p>
</label>

I just add a bracket at my useEffect because If I didn't add a bracket, I think it continuing to fetch the data and keeping the default value of all the data thats why its blinking.
const dispatch = useDispatch()
const todos = useSelector(state => state.TodoReducer.todos)
useEffect(() => {
dispatch(fetchTodoData())
}, [])
return (
<div>
{todos.map(todo => <Todo key={todo.id} todo={todo}/>)}
</div>

Related

React onClick cannot handle multiple methods

I have the following React component:
const ParentComponent = () => {
const [data, setData] = useState()
// handle these data here and display them
return <ChildComponent sendData={setData} data={data} />
}
const ChildComponent = ({ data, sendData }) => {
const toggleStrikethrough = e => {
e.target.style.textDecoration = e.target.style.textDecoration === 'line-through' ? '' : 'line-through'
const { booking } = e.currentTarget.dataset
sendData(booking)
}
return (
<div>
{data.map(booking => (
<button key={index} data-booking={booking.name} onClick={toggleStrikethrough}>
{booking.name}
</button>
))}
</div>
)
}
AS you can see I have parent component which receive the data from child component but my issue is with the child component, when I try to manipulate the style with sending data in same time it does not work, but only one of them work eg. if I remove sendData(booking) the styles change and if I remove the style manipulating in the same function toggleStrikethrough() then the data sent to parent component but they cannot work together, any idea why?
The issue is it's rerendering after the parent's state is getting updated.
When you do sendData it updates the state of the parent. And when the state updates it re-renders the component, including all the children.
Ideally, you'd want to style your components based on data stored in the state.
I think React is likely re-rendering your buttons without the text decorations.
What you probably want is to modify the elements in the data passed down to ChildComponent so that "selected"/"checked" is a part of the state (boolean probably works).
e.g. if your data currently looks like: [{ name: "foo" }, { name: "bar" }], you can manipulate it so that it becomes [{ name: "foo" }, { name: "bar", checked: true }].
So you can do something like this:
const ParentComponent = () => {
const [data, setData] = useState()
// handle these data here and display them
return <ChildComponent sendData={setData} data={data} />
}
const ChildComponent = ({ data, sendData }) => {
const toggleStrikethrough = e => {
const { booking } = e.currentTarget.dataset
sendData({ ...booking, checked: true })
}
return (
<div>
{data.map(booking => (
<button key={index} data-booking={booking.name} onClick={toggleStrikethrough} styles={{ textDecoration: booking.checked ? "line-through" : "initial"}}>
{booking.name}
</button>
))}
</div>
)
}

React setState not updating checkbox checked state

I'm following a React beginners tutorial making a todo app as an example.
in the App.js, there is a handleChange method that will update the state whether the checkbox is checked or not, and passes it into the TodoItem component
class App extends React.Component {
constructor() {
super()
this.state = {
todos: todosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
console.log(id)
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
todo.completed = !todo.completed
}
return todo
})
return {
todos: updatedTodos
}
})
}
render() {
const todoItems = this.state.todos.map(item => <TodoItem key={item.id} item={item} handleChange={this.handleChange}/>)
return (
<div className="todo-list">
{todoItems}
</div>
)
}
}
export default App
TodoItem.js
function TodoItem(props) {
return (
<div className="todo-item">
<input
type="checkbox"
checked={props.item.completed}
onChange={() => props.handleChange(props.item.id)}
/>
<p>{props.item.text}</p>
</div>
)
}
export default TodoItem
it successfully displays the list and the console log correctly displays the checkbox clicked, however, the checkbox does not change. Can anyone tell me the problem?
I think you are running into a state mutation problem which is causing some unexpected behavior. The reason for this is because inside your if statement within map you are not returning your modified array item and you are actually modifying your state array and your new array.
How to fix: return your modified array item inside the if statement
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed
}
}
return todo
})
return {
todos: updatedTodos
}
})
Or use a one liner with conditional (ternary) operator:
this.setState(prevState => ({
...prevState,
todos: prevState.todos.map(todo => todo.id === id ? { ...todo, ...{ completed: !todo.completed } } : todo)
}))
Look at this example I created in playground to get a better understanding:
I defined two arrays, one array uses map() without the return and the other array is using map() with the return.
Our goal is to keep our two arrays exactly the same and using map() to create a new modified array. Look at the log results and notice how our initial array gets modified aswell. Our second todo item in this array should have a completed value of true but it gets changed to false after our map() which we would want to avoid. By returning our array item in the correct way we avoid this.
To get a better understanding of what state mutation is and how to avoid check this.

React Asynchronous Fetching

Using React and React-Dom CDN 16
I am new to React and trying to build a dashboard component that takes the value of one of three buttons in a Buttons component and sends the value to a List component. The List component fetches data from an API and renders the results.
The feature works fine up until the data fetching, which it only does once the app is rendered the first time. I've logged that the state that's set by the Buttons component is making its way to the List component and the fetch action is updating dynamically correctly, but the fetching functionality isn't getting triggered when that state updates.
Here's the code.
const { useState, useEffect } = React
const App = props => {
return (
<div className="app-content">
<Dashboard />
</div>
);
};
const Dashboard = props => {
const [timespan, setTimespan] = useState('week');
const changeTime = time => setTimespan(time);
return(
<div>
<p>{timespan}</p> // this state updates when the buttons are clicked
<Buttons onUpdate={changeTime} />
<List timespan={timespan}/>
</div>
);
};
const Buttons = props => {
return (
<div>
<button onClick={props.onUpdate.bind( this, 'week' )}>
week
</button>
<button onClick={props.onUpdate.bind( this, 'month' )}>
month
</button>
<button onClick={props.onUpdate.bind( this, 'all' )}>
all
</button>
</div>
);
};
const List = props => {
const timespan = props.timespan;
const homepage = `${location.protocol}//${window.location.hostname}`;
const action = `${homepage}?fetchDataset=1&timespan=${timespan}`;
// if I console.log(action) here the URL is updated correctly
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [obj, setObj] = useState([]);
useEffect(() => {
fetch(action)
.then(res => res.json())
.then(
(result) => { // if I console.log(result) here I only get a response at initialization
setIsLoaded(true);
setObj(result);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, []);
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div>
// my API returns "timespan is: $timespan", but this is only ever "week" because that's the initial state of the timespan
{obj}
</div>
);
};
};
ReactDOM.render(
<App />,
document.getElementById('app')
);
I think I must be overlooking something very obvious because this seems like one of the core purposes of React, but it's hard to find documentation that is relevant with version 16 updates like function classes and hooks.
I really appreciate any help. Thanks!
you need to add timeSpan (or action) to your useEffect dependency array:
useEffect(() => {
fetch(action)
.then(res => res.json())
.then(
result => {
setIsLoaded(true);
setObj(result);
},
error => {
setIsLoaded(true);
setError(error);
}
);
}, [timeSpan]); // [action] will also solve this
This way the effect will know it needs to run every time the timeSpan prop changes.
By passing an empty dependency array you are telling the effect to only run once - when the component it mounted.

Set initial state from props in React componenet

I have such a component in which I set initial state from props:
class CarsModal extends PureComponent {
constructor(props) {
super(props);
autoBind(this);
const { data } = {} } = props;
this.state = {
selectedCar: data.category || '',
cars: [],
isSpam: data.spam,
pick: data.pick,
suitable: data.suitable,
articles: false,
show: false,
};
this.messageTimer = 0;
}
async componentDidMount() {
const cars = await getCars();
this.setState({
cars,
});
}
componentWillUnmount() {
clearTimeout(this.messageTimer);
}
close() {
}
select(car) {
this.setState({
selectedCar: car,
});
}
async save(scrapeRequest = false) {
const { carId } = this.props;
const {
selectedCar,
isSpam,
pick,
suitable,
articles,
} = this.state;
await curateCar(storyId, {
selectedCar,
is_spam: isSpam,
pick: pick,
suitable: suitable,
articles: articles,
scrape: scrape,
});
if (!scrape) {
this.setState({
show: true,
});
clearTimeout(this.messageTimer);
this.messageTimer = setTimeout(() => {
this.setState({
show: false,
});
}, 1500);
}
}
scrape() {
this.save(true);
}
toggle(key) {
this.setState((state) => ({
[key]: !state[key],
}));
}
render() {
const { show, curatorData } = this.props;
const { author, cars, show } = this.state;
return (
<Modal
show={show}
onHide={this.handleClose}
dialogClassName={cx('curate-modal')}
centered
>
<ionClick={this.close} />
<h3>Tools</h3>
<div>
<span>Auto:</span>
<DropdownButton
title={(
<>
{selectedAuthor}
<i />
</>
)}
>
{cars.map((car) => (
<Dropdown.Item
key={author}
active={author === car}
onClick={() => this.select(author)}
>
{author}
</Dropdown.Item>
))}
</DropdownButton>
</div>
{OPTIONS.map((option) => (
<Option
key={option.key}
id={option.key}
checked={this.state[option.key]}
onChange={() => this.toggle(option.key)}
>
{option.content}
</Option>
))}
<div className={cx('update-info')}>
<button
type="button"
onClick={() => this.save()}
>
Update
</button>
{showMessage && (
<span>Updated</span>
)}
</div>
<hr />
<span
className={cx('link')}
onClick={this.scrape}
>
Scrape article
</span>
<a href='/'>Test 1</a>
<a href='/'>Vtest 2</a>
</Modal>
);
}
}
const mapDispatchToProps = (dispatch) => ({
actions: bindActionCreators({ ...actions }, dispatch),
});
const mapStateToProps = (state) => ({
userData: state.reducer.userData,
});
but I know that setting initial state from props is antipattern. I tried to use getDerivedStateFromProps(nextProps, prevState)
React component initialize state from props
But after that my state updates everytime and didn't change actually, is it updates with same props everytime. I need to set initial state only on INITIAL load. How can I do that?
I would strongly discourage you from using getDerivedStateFromProps. This is why the reactjs blog has a post called "You Probably Don't Need Derived State"
I think there's some general confusion about what exactly is wrong with setting initial state based on props (and what the point of props are).
The point of props is they provide an easy and consistent way to pass data around and react will automatically update when the view if those props change.
The point of state is to provide a consistent way to store "stateful" information in your application (aka information that can change).
Let's map out a few examples:
(A) state (source of truth) => view // this is fine. the source of truth is the state.
(B) state (source of truth) => props => view // this is fine. the source of truth is the parent components state. (or alternatively, state would be the redux store - also fine)
(C) redux store (source of truth) => props => state => view // this is confusing. why aren't we just going store => props => view ?
(D) state (source of truth) => props => state => view // this is confusing. we now have the source of truth split between two separate states. However, this is not necessarily an anti-pattern in every case.
(E) state/store => props => state (after initial seed of data, this is the source of truth) => view
this is not an anti-pattern so long as you are only using the state from above as an initial seed of data. On subsequent renders, the flow of information in (E) is actually just state => view. which is simple and unidirectional with a single source of truth.
This post may be of help.
How about setting your state using your props, in componentDidMount?
componentDidMount(){
this.setState({
selectedCar: this.props.data.selectedCar
})
}

Lifting up the state to the main component in React application using hooks

I am learning reactjs and trying to implement small things for practice. The idea is simple, to add records (feedbackTasks) to the database and list these records (when first time page is loaded and later when a new record is added). Please see the image below.
The main component is ManageFeedbackTasks. I keep the list of the feedbackTask items in its state (st_feedbackTaskList). The update of this list is performed through add_to_st_feedbackTask function. If the first time this list is generated, all the fetched data (coming from PrintFeedbackTasks component) is set to the st_feedbackTaskList. If not, only the added item (coming from ShowAddingFeedbackTaskForm) is inserted in the list.
export function ManageFeedbackTasks() {
const [st_feedbackTaskList, setFeedbackTaskList] = useState([]);
const add_to_st_feedbackTask = (data) => {
if (st_feedbackTaskList.length == 0) {
setFeedbackTaskList(data);
} else {
const { id, title, description } = data;
setFeedbackTaskList([...st_feedbackTaskList, { id, title, description }]);
}
}
return (
<>
<ShowAddingFeedbackTaskForm onAddingItem={add_to_st_feedbackTask} />
<PrintFeedbackTasks onListingFeedbackTasks={add_to_st_feedbackTask} feedbackTasks={st_feedbackTaskList} />
</>
);
}
Below is the PrintFeedbackTasks function. This function receives the feedbackTasks list from the main component ManageFeedbackTasks. This list is first time fetched from the database using fetchFeedbackTasks. Inside fetchFeedbackTasks, props.onListingFeedbackTasks(response.data) sends the fetched list back to the main component to update the state (st_feedbackTaskList).
const PrintFeedbackTasks = (props) => {
const [st_isInitialized, setInitialized] = useState(false);
const fetchFeedbackTasks = () => {
axios.get('api/FeedbackTask/Index')
.then(response => props.onListingFeedbackTasks(response.data))
.catch(error => console.log(error));
}
useEffect(() => {
if (!st_isInitialized) {
fetchFeedbackTasks();
}
setInitialized(true);
});
return (
<React.Fragment>
{
props.feedbackTasks.map(taskItem =>....
}
</React.Fragment>
);
}
The component below displays the Add form and handles the form submission. When a new item is added, this new item is again sent back to the main component using props.onAddingItem.
const ShowAddingFeedbackTaskForm = (props) => {
const [st_title, setTitle] = useState('');
const [st_description, setDescription] = useState('');
const handleSubmit = async (event) => {
event.preventDefault();
await axios(...)
.then(function (response) {
setTitle('');
setDescription('');
//This will update the list of the feedback task in the main component
props.onAddingItem({
id: response.data,
title: st_title,
description: st_description
});
//GET THE ID HERE
console.log(response.data);
}).catch(function (error) {
console.log(error);
});
}
return (
<form onSubmit={handleSubmit}>
<input
placeholder="Title..."
type="text"
value={st_title}
onChange={(event) => setTitle(event.target.value)}
/>
<input
placeholder="Description..."
type="text"
value={st_description}
onChange={(event) => setDescription(event.target.value)}
/>
<button>Add Feedback Task</button>
</form>
);
}
I wonder if this way of lifting and managing the state is robust. Any suggestions to improve the code? Also, I wonder if I should put these components into their own pages (for example, one page or adding a record and another one for listing). Would this make more sense in the react world?
The idea to lift the state up to the parent is correct. However due to your code structure you could be causing a lot of re-renders and a few performance optimizations can be made in your solution. One more thing is that instead of fetching feedbackTasks in PrintFeedbackTasks component you should do it in the parent itself. Also useEffect takes a second parameter which you can use to execute it on initial mount
You can use useCallback hook to memoize functions too.
ManageFeedbackTasks
export function ManageFeedbackTasks() {
const [st_feedbackTaskList, setFeedbackTaskList] = useState([]);
const fetchFeedbackTasks = useCallback(() => {
axios.get('api/FeedbackTask/Index')
.then(response => props.onListingFeedbackTasks(response.data))
.catch(error => console.log(error));
}, []);
useEffect(() => {
fetchFeedbackTasks();
}, []);
const add_to_st_feedbackTask = useCallback((data) => {
setFeedbackTaskList(prevTaskList => {
if (prevTaskList.length == 0) {
return data;
} else {
const { id, title, description } = data;
return [...prevTaskList, { id, title, description }];
}
});
}, [])
return (
<>
<ShowAddingFeedbackTaskForm onAddingItem={add_to_st_feedbackTask} />
<PrintFeedbackTasks onListingFeedbackTasks={add_to_st_feedbackTask} feedbackTasks={st_feedbackTaskList} />
</>
);
}
PrintFeedbackTasks
const PrintFeedbackTasks = (props) => {
return (
<React.Fragment>
{
props.feedbackTasks.map(taskItem =>....
}
</React.Fragment>
);
}
As far as the idea to split show and update TaskList is concerned it as product decision which can be made depending on how long is the list of fields that the user needs to fill at once

Categories

Resources