What is the correct way to use push array in React native ?
const BuilderIndicatorCard = (props) => {
const [isChecked, setIsChecked] = useState(false);
const [checkedValue, setCheckedValue] = useState([]);
let storeCheckedValue = [];
useEffect(() => {
if (isChecked) {
storeCheckedValue.push(props.indicator);
}
console.log(storeCheckedValue);
}, [isChecked, checkedValue])
// const removeCheckedStrategy = (checkedValue, array) => {
// var copyArray = [...array];
// var index = copyArray.indexOf(checkedValue);
// if (index !== -1) {
// copyArray.splice(index, 1);
// setArray(copyArray);
// }
// }
return (
<CheckBox
containerStyle={styles.checkbox}
size={15}
textStyle={styles.name}
title={props.indicator}
checked={isChecked}
onPress={() => {setIsChecked(!isChecked)}}
/>
);
};
When I do storeCheckedValue.push(props.indicator); why the array keep replace and not append ?
This is show in the console :
Array [
"Price Below MA (5)",
]
Array [
"Price Below MA (7)",
]
Array [
"Price Below MA (9)",
]
did I miss something in here ?
I found a proper way to solve my question above.
This is how I did it.
I use redux-toolkit
Create a slice like :
import { createSlice } from '#reduxjs/toolkit'
const addChecked = (state, action) => {
state.indicator = [...state.indicator, action.payload];
}
const removeChecked = (state, action) => {
state.indicator = state.indicator.filter(data => data != action.payload);
}
// status: 'idle' | 'loading' | 'succeeded' | 'failed',
export const BuilderSlice = createSlice({
name: 'Builder',
initialState: {
indicator: [],
status: 'idle',
error: null
},
reducers: {
addCheckedIndicator: addChecked,
removeCheckedIndicator: removeChecked
},
extraReducers: {
}
});
export const { addCheckedIndicator, removeCheckedIndicator } = BuilderSlice.actions
Then In the card component I change to like this :
import { addCheckedIndicator, removeCheckedIndicator } from '../redux/slice/builder/BuilderSlice';
const BuilderIndicatorCard = (props) => {
const dispatch = useDispatch();
const data = useSelector(state => state.Builder.indicator);
const [isChecked, setIsChecked] = useState(true);
const addValue = useCallback(() => {
setIsChecked(!isChecked);
if (isChecked) {
dispatch(addCheckedIndicator(props.indicator));
} else {
dispatch(removeCheckedIndicator(props.indicator));
}
}, [isChecked]);
return (
<CheckBox
containerStyle={styles.checkbox}
size={15}
textStyle={styles.name}
title={props.indicator}
checked={props.checked}
onPress={() => {addValue()}}
/>
);
};
I dispatch the function in slice in here:
dispatch(addCheckedIndicator(props.indicator));
dispatch(removeCheckedIndicator(props.indicator));
And then make sure use :
const addValue = useCallback(() => {
},[second]);
Not useEffect.
Related
I have fetch method in useEffect hook:
export const CardDetails = () => {
const [ card, getCardDetails ] = useState();
const { id } = useParams();
useEffect(() => {
fetch(`http://localhost:3001/cards/${id}`)
.then((res) => res.json())
.then((data) => getCardDetails(data))
}, [id])
return (
<DetailsRow data={card} />
)
}
But then inside DetailsRow component this data is not defined, which means that I render this component before data is fetched. How to solve it properly?
Just don't render it when the data is undefined:
export const CardDetails = () => {
const [card, setCard] = useState();
const { id } = useParams();
useEffect(() => {
fetch(`http://localhost:3001/cards/${id}`)
.then((res) => res.json())
.then((data) => setCard(data));
}, [id]);
if (card === undefined) {
return <>Still loading...</>;
}
return <DetailsRow data={card} />;
};
There are 3 ways to not render component if there aren't any data yet.
{data && <Component data={data} />}
Check if(!data) { return null } before render. This method will prevent All component render until there aren't any data.
Use some <Loading /> component and ternar operator inside JSX. In this case you will be able to render all another parts of component which are not needed data -> {data ? <Component data={data} /> : <Loading>}
If you want to display some default data for user instead of a loading spinner while waiting for server data. Here is a code of a react hook which can fetch data before redering.
import { useEffect, useState } from "react"
var receivedData: any = null
type Listener = (state: boolean, data: any) => void
export type Fetcher = () => Promise<any>
type TopFetch = [
loadingStatus: boolean,
data: any,
]
type AddListener = (cb: Listener) => number
type RemoveListener = (id: number) => void
interface ReturnFromTopFetch {
addListener: AddListener,
removeListener: RemoveListener
}
type StartTopFetch = (fetcher: Fetcher) => ReturnFromTopFetch
export const startTopFetch = function (fetcher: Fetcher) {
let receivedData: any = null
let listener: Listener[] = []
function addListener(cb: Listener): number {
if (receivedData) {
cb(false, receivedData)
return 0
}
else {
listener.push(cb)
console.log("listenre:", listener)
return listener.length - 1
}
}
function removeListener(id: number) {
console.log("before remove listener: ", id)
if (id && id >= 0 && id < listener.length) {
listener.splice(id, 1)
}
}
let res = fetcher()
if (typeof res.then === "undefined") {
receivedData = res
}
else {
fetcher().then(
(data: any) => {
receivedData = data
},
).finally(() => {
listener.forEach((cb) => cb(false, receivedData))
})
}
return { addListener, removeListener }
} as StartTopFetch
export const useTopFetch = (listener: ReturnFromTopFetch): TopFetch => {
const [loadingStatus, setLoadingStatus] = useState(true)
useEffect(() => {
const id = listener.addListener((v: boolean, data: any) => {
setLoadingStatus(v)
receivedData = data
})
console.log("add listener")
return () => listener.removeListener(id)
}, [listener])
return [loadingStatus, receivedData]
}
This is what myself needed and couldn't find some simple library so I took some time to code one. it works great and here is a demo:
import { startTopFetch, useTopFetch } from "./topFetch";
// a fakeFetch
const fakeFetch = async () => {
const p = new Promise<object>((resolve, reject) => {
setTimeout(() => {
resolve({ value: "Data from the server" })
}, 1000)
})
return p
}
//Usage: call startTopFetch before your component function and pass a callback function, callback function type: ()=>Promise<any>
const myTopFetch = startTopFetch(fakeFetch)
export const Demo = () => {
const defaultData = { value: "Default Data" }
//In your component , call useTopFetch and pass the return value from startTopFetch.
const [isloading, dataFromServer] = useTopFetch(myTopFetch)
return <>
{isloading ? (
<div>{defaultData.value}</div>
) : (
<div>{dataFromServer.value}</div>
)}
</>
}
Try this:
export const CardDetails = () => {
const [card, setCard] = useState();
const { id } = useParams();
useEffect(() => {
if (!data) {
fetch(`http://localhost:3001/cards/${id}`)
.then((res) => res.json())
.then((data) => setCard(data))
}
}, [id, data]);
return (
<div>
{data && <DetailsRow data={card} />}
{!data && <p>loading...</p>}
</div>
);
};
I'm trying to learn typescript, currently creating a note taking app. It's very simple: when you click on adding a new note, you a get an empty textarea, where you can edit your note. I'm able to add notes, but I can't update the value of each textarea. What am I doing wrong?
Here's what I have so far:
const [showSidePanel, setShowSidePanel] = React.useState<boolean>(false);
const [notes, setNotes] = React.useState([{text: '', id: nanoid()}]);
const [noteText, setNoteText] = React.useState<string>('');
const addNote = (): void => {
const newNote = {text: 'hey', id: nanoid()};
setNotes([...notes, newNote])
}
const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
setNoteText(event.target.value)
}
const toggleSidePanel = React.useCallback(() => setShowSidePanel(!showSidePanel), [showSidePanel]);
const wrapperRef = React.useRef<HTMLDivElement>(null);
useClickOutside(wrapperRef, () => setShowSidePanel(false));
return (
<div ref={wrapperRef}>
<GlobalStyle />
<SidePanel showSidePanel={showSidePanel}>
<Button onClick={addNote}>Add note</Button>
{notes.map((n) =>
<Note onChange={() => handleChange} text={noteText} key={n.id}/>
)}
</SidePanel>
<ToggleButton onClick={toggleSidePanel}>Open</ToggleButton>
</div>
);
}
If I understand it correctly, each Note is a text-area and you want to update each one of them independently and noteText state is used for active text-area component, correct?
If that's the case then we can either (1)remove noteText state and update notes array directly for appropriate notes.id or (2)we can preserve noteText state and update the notes array after debouncing.
Latter solution is preferable and coded below:
// a hook for debouncing
export const useDebouncedValue = (value, timeOut=500) => {
const [debouncedValue, setDebouncedValue] = useState(null);
useEffect(() => {
let someTimeout;
someTimeout = setTimeout(() => {
setDebouncedValue(value);
}, timeOut);
return () => clearInterval(someTimeout);
}, [value, timeOut]);
return {
debouncedValue,
};
};
Main logic for updating the notes array logic
const Comp = () => {
const [showSidePanel, setShowSidePanel] = React.useState(false);
const [notes, setNotes] = React.useState([{ text: "", id: nanoid() }]);
const [currNoteText, setCurrNoteText] = React.useState({
text: "",
id: "",
});
// this always holds the debounced value
const updatedNoteText = useDebouncedValue(currNoteText.text, 600);
// effect will get triggered whenever the currNoteText.text is changed and pause of 600ms is taken
useEffect(() => {
const notesIndex = notes.findIndex((ele) => ele.id === currNoteText.id);
if (notesIndex >= 0) {
const updatedNotes = _.cloneDeep(notes);
updatedNotes[notesIndex].text = updatedNoteText;
// updation of notes array
setNotes(updatedNotes);
}
}, [updatedNoteText]);
const addNote = () => {
const newNote = { text: "hey", id: nanoid() };
setNotes([...notes, newNote]);
};
const handleChange = (event, noteId) => {
// setting current changed note in currNoteText Object
setCurrNoteText({ id: noteId, text: event.target.value });
};
const toggleSidePanel = React.useCallback(
() => setShowSidePanel(!showSidePanel),
[showSidePanel]
);
const wrapperRef = React.useRef(null);
useClickOutside(wrapperRef, () => setShowSidePanel(false));
return (
<div ref={wrapperRef}>
<GlobalStyle />
<SidePanel showSidePanel={showSidePanel}>
<Button onClick={addNote}>Add note</Button>
{notes.map((n) => (
<Note
// here along with event we are also passing note-id
onChange={(e) => handleChange(e, n.id)}
text={noteText}
key={n.id}
/>
))}
</SidePanel>
<ToggleButton onClick={toggleSidePanel}>Open</ToggleButton>
</div>
);
};
In image it is saying It is expected to have ";", can someone explain ?
import React, {useReducer} from 'react';
import CartContext from './cart-context';
const defaultCartState = {
items : [],
totalAmount : 0
};
const cartReducer = (state, action) = {
if (action.type === 'ADD') {
const updatedItems = state.items.concat(item);
const updatedTotalAmount = state.totalAmount + action.item.price * action.item.amount;
return {
items : updatedItems,
totalAmount : updatedTotalAmount
};
}
return defaultCartState;
}
const CartProvider = (props) => {
const [cartState, dispatchCartState] = useReducer(cartReducer, defaultCartState);
const adder = (item) =>{
dispatchCartState({type : 'ADD', item : item})
};
const remover = (id) => {
dispatchCartState({type : 'REMOVE', id : id})
};
const cartContext = {
item: [],
totalAmount : 0,
addItem : adder,
removeItem : remover
}
return (
<CartContext.Provider value={cartContext}>
{props.children}
</CartContext.Provider>
);
}
export default CartProvider;
I don't know what code is saying. Picture has been attached.
You are Using the Arrow Function in a Wrong way
Update your cartReducer function like this
const cartReducer = (state, action) => {
if (action.type === 'ADD') {
const updatedItems = state.items.concat(item);
const updatedTotalAmount = state.totalAmount + action.item.price * action.item.amount;
return {
items : updatedItems,
totalAmount : updatedTotalAmount
};
}
How can I set data into the state?
And how can I solve createContext error?
export const LastChanceContext = createContext() //in there return error: Expected 1 arguments, but got 0.
export const GET_LASTCHANCE = "GET_LASTCHANCE"
const LastChanceWrapper = ({children} : {children:any}) => {
const initialState = {
lastChances: [],
lastChance: {}
};
console.log('asd',initialState);
const [state, dispatch] = useReducer(lastChanceReducer, initialState);
useEffect(() => {
const getLastChance = async () => {
const result = await axiosFetch('http://65.21.148.176:2803/api/Home/Get');
const action = {
type:GET_LASTCHANCE,
payload:result.data.data.lastChanceList
};
if(Object.keys(result).length !== 0) {
dispatch(
{state,
action}
)
console.log(result.data.data.lastChanceList);
}
};
getLastChance();
}, [])
return (
<LastChanceContext.Provider value={{
lastChances: state.lastChances,
lastChance: state.lastChance
}}>
{children}
</LastChanceContext.Provider>
)
}
export default LastChanceWrapper
I am trying to migrate my class based react component to react-hooks. The purpose of the component is to fetch stories from HackerNews API and after each 5000 milliseconds to do a polling by hitting the API again for new data.
The problem I am facing is in using the custom hooks below usePrevious() to compare my previous state with current state and only after the comparison to execute some other function inside useEffect()
I am most probably missing some basic implementation here of the custom hooks or of useEffect()
And I am following this official guide
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
Here's the code for my class based component and this is perfectly working.
And below is my hooks based component
The problem is with this line
const fromPrevStoriesIds = usePrevious(prevStoriesIds);
The variable fromPrevStoriesIds is giving me good value inside return(), but inside useEffect() its undefined.
import React, { Component, useState, useEffect, useRef } from "react";
import axios from "axios";
import MUIDataTable from "mui-datatables";
import "./Dashboard.css";
import NewItemAddedConfirmSnackbar from "./NewItemAddedConfirmSnackbar";
import TextField from "#material-ui/core/TextField";
import Button from "#material-ui/core/Button";
const isEqual = require("lodash.isequal");
const differenceWith = require("lodash.differencewith");
const omit = require("lodash.omit");
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const getEachStoryGivenId = (id, index) => {
return new Promise((resolve, reject) => {
axios
.get(`https://hacker-news.firebaseio.com/v0/item/${id}.json`)
.then(res => {
let story = res.data;
let result = omit(story, ["descendants", "time", "id", "type"]);
if (
result &&
Object.entries(result).length !== 0 &&
result.constructor === Object
) {
resolve(result);
} else {
reject(new Error("No data received"));
}
});
});
};
const Dashboard = () => {
const [prevStoriesIds, setPrevStoriesIds] = useState([]);
const [fetchedData, setFetchedData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [tableState, setTableState] = useState({});
const [
openNewItemAddedConfirmSnackbar,
setOpenNewItemAddedConfirmSnackbar
] = useState(false);
const [noOfNewStoryAfterPolling, setNoOfNewStoryAfterPolling] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const onChangeRowsPerPage = rowsPerPage => {
setRowsPerPage(rowsPerPage);
};
const closeNewItemConfirmSnackbar = () => {
setOpenNewItemAddedConfirmSnackbar(false);
axios
.get("https://hacker-news.firebaseio.com/v0/newstories.json")
.then(storyIds => {
setPrevStoriesIds(storyIds.data.slice(0, 2));
getAllNewStory(storyIds);
});
};
const getAllNewStory = storyIds => {
setIsLoading(true);
let topStories = storyIds.data.slice(0, 2).map(getEachStoryGivenId);
let results = Promise.all(topStories);
results
.then(res => {
setFetchedData(res);
setIsLoading(false);
})
.catch(err => {
console.log(err);
});
};
const fromPrevStoriesIds = usePrevious(prevStoriesIds);
useEffect(() => {
const fetchData = () => {
axios
.get("https://hacker-news.firebaseio.com/v0/newstories.json")
.then(storyIds => {
// console.log("STORY IDs FETCHED ", storyIds.data.slice(0, 2));
setPrevStoriesIds(storyIds.data.slice(0, 2));
getAllNewStory(storyIds);
});
};
fetchData();
const doPolling = () => {
var timer = setInterval(() => {
axios
.get("https://hacker-news.firebaseio.com/v0/newstories.json")
.then(storyIds => {
console.log(
"fromPrevStoriesIds INSIDE doPolling() ",
fromPrevStoriesIds
);
if (
fromPrevStoriesIds !== undefined &&
!isEqual(fromPrevStoriesIds.sort(), storyIds.data.slice(0, 2).sort())
) {
setPrevStoriesIds(storyIds.data.slice(0, 2));
setNoOfNewStoryAfterPolling(
differenceWith(
prevStoriesIds.sort(),
storyIds.data.slice(0, 2).sort(),
isEqual
).length
);
getAllNewStory(storyIds);
setOpenNewItemAddedConfirmSnackbar(true);
}
});
}, 5000);
};
doPolling();
// return () => {
// console.log("cleaning up");
// clearInterval(timer);
// };
}, [rowsPerPage, noOfNewStoryAfterPolling]);
let renderedStoriesOnPage = [];
const getDataToRender = (() => {
renderedStoriesOnPage = fetchedData.map(i => {
return Object.values(i);
});
return renderedStoriesOnPage;
})();
const columnsOptions = [
{
name: "Author",
sortDirection: tableState
? tableState.columns && tableState.columns[0].sortDirection
: null
},
{
name: "score",
sortDirection: tableState
? tableState.columns && tableState.columns[1].sortDirection
: null
},
{
name: "title",
sortDirection: tableState
? tableState.columns && tableState.columns[2].sortDirection
: null
},
{
name: "URL",
options: {
filter: false,
customBodyRender: (value, tableMeta, updateValue) => {
// console.log("TABLE META IS ", JSON.stringify(tableMeta));
return (
<a target="_blank" href={value}>
{value}
</a>
);
}
}
}
];
const options = {
filter: true,
selectableRows: false,
filterType: "dropdown",
responsive: "stacked",
selectableRows: "multiple",
rowsPerPage: tableState ? tableState.rowsPerPage : 10,
onChangeRowsPerPage: onChangeRowsPerPage,
activeColumn: tableState ? tableState.activeColumn : 0,
onTableChange: (action, tableState) => {
// console.log("taBLE STATE IS ", JSON.stringify(tableState));
setTableState(tableState);
}
};
return (
<React.Fragment>
{console.log("fromPrevStoriesIds INSIDE RETURN --- ", fromPrevStoriesIds)}
<div
style={{
marginLeft: "15px",
marginTop: "80px",
display: "flex",
flexDirection: "row"
}}
>
<h4 style={{ width: "400px", paddingRight: "15px" }}>
Hacker News top 2
</h4>
</div>
<div>
{isLoading ? (
<div className="interactions">
<div className="lds-ring">
<div />
<div />
<div />
<div />
</div>
</div>
) : fetchedData.length !== 0 && renderedStoriesOnPage.length !== 0 ? (
<MUIDataTable
title={"Hacker News API top 2 result"}
data={renderedStoriesOnPage}
columns={columnsOptions}
options={options}
/>
) : null}
<NewItemAddedConfirmSnackbar
openNewItemAddedConfirmSnackbar={openNewItemAddedConfirmSnackbar}
closeNewItemConfirmSnackbar={closeNewItemConfirmSnackbar}
noOfNewStoryAfterPolling={noOfNewStoryAfterPolling}
/>
</div>
</React.Fragment>
);
};
export default Dashboard;
Instead of returning ref.current from usePrevious return, ref since ref.current will be mutated at its reference and you will be able to receive the updated value within useEffect otherwise it will receive the value from its closure
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref;
}
Code:
const fromPrevStoriesIds = usePrevious(prevStoriesIds);
useEffect(() => {
const fetchData = () => {
axios
.get("https://hacker-news.firebaseio.com/v0/newstories.json")
.then(storyIds => {
// console.log("STORY IDs FETCHED ", storyIds.data.slice(0, 2));
setPrevStoriesIds(storyIds.data.slice(0, 2));
getAllNewStory(storyIds);
});
};
fetchData();
const doPolling = () => {
var timer = setInterval(() => {
axios
.get("https://hacker-news.firebaseio.com/v0/newstories.json")
.then(storyIds => {
console.log(
"fromPrevStoriesIds INSIDE doPolling() ",
fromPrevStoriesIds.current
);
if (
fromPrevStoriesIds.current !== undefined &&
!isEqual(fromPrevStoriesIds.current.sort(), storyIds.data.slice(0, 2).sort())
) {
setPrevStoriesIds(storyIds.data.slice(0, 2));
setNoOfNewStoryAfterPolling(
differenceWith(
prevStoriesIds.sort(),
storyIds.data.slice(0, 2).sort(),
isEqual
).length
);
getAllNewStory(storyIds);
setOpenNewItemAddedConfirmSnackbar(true);
}
});
}, 5000);
};
doPolling();
// return () => {
// console.log("cleaning up");
// clearInterval(timer);
// };
}, [rowsPerPage, noOfNewStoryAfterPolling]);